﻿using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Mga;

public class JsonParser
{
    public static string ObjectToString(object data, string defaultValue = "")
    {
        try
        {
            if (data == null)
            {
                Debug.LogWarning("Can not convert null to string.");
                return defaultValue;
            }
            return data.ToString();
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("Can not convert{0} to string with{1}", data, ex.GetType().Name));
        }
        return defaultValue;
    }
    public static int ObjectToInt(object data, int defaultValue = 0)
    {
        try
        {
            if (data == null)
            {
                Debug.LogWarning("Can not convert null to int.");
                return defaultValue;
            }
            return System.Convert.ToInt32(data);
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("Can not convert{0} to int with{1}", data, ex.GetType().Name));
        }
        return defaultValue;
    }

    public static float ObjectToFloat(object data, float defaultValue = 0)
    {
        try
        {
            if (data == null)
            {
                Debug.LogWarning("Can not convert null to float.");
                return defaultValue;
            }
            return System.Convert.ToSingle(data);
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("Can not convert{0} to float with{1}", data, ex.GetType().Name));
        }
        return defaultValue;
    }

    public static bool ObjectToBool(object data, bool defaultValue = false)
    {
        try
        {
            if (data == null)
            {
                Debug.LogWarning("Can not convert null to bool.");
                return defaultValue;
            }
            return System.Convert.ToBoolean(data);
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("Can not convert{0} to bool with{1}", data, ex.GetType().Name));
        }
        return defaultValue;
    }

    public static int StringToInt(string str, int defaultValue = 0)
    {
        try
        {
            if (string.IsNullOrEmpty(str))
            {
                Debug.LogWarning("Can not convert null or empty on int.");
                return defaultValue;
            }
            return System.Convert.ToInt32(str);
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("Can not convert {0} to int with {1}", str, ex.GetType().Name));
        }
        return defaultValue;
    }
}

public static class Extensions
{
    public static int IndexOf<T>(this IList<T> source, T value)
    {
        return source.IndexOf(value);
    }

    public static int IndexOf<T>(this IList<T> source, System.Predicate<T> match)
    {
        if (match == null)
            return -1;
        int index = 0;
        foreach(T item in source)
        {
            if(match(item))
            {
                return index;
            }
            ++index;
        }
        return -1;
    }
}

[CanEditMultipleObjects]
[CustomEditor(typeof(LuaInjectionData), true)]
public class LuaInjectionDataInspector : Editor
{
    protected LuaInjectionData m_LuaInjectionData;

    public void OnEnable()
    {
        m_LuaInjectionData = target as LuaInjectionData;
    }

    public override void OnInspectorGUI()
    {
        EditorGUIUtility.labelWidth = 80;

        GUI.changed = false;

        FieldInfo field = typeof(LuaInjectionData).GetField("m_InjectionList", BindingFlags.Instance | BindingFlags.NonPublic);
        List<Injection4> injections = field.GetValue(m_LuaInjectionData) as List<Injection4>;

        DrawArray(injections, true);

        GUILayout.BeginHorizontal();
        Undo.RecordObject(m_LuaInjectionData, "Injection.Count");
        if (GUILayout.Button("+"))
        {
            injections.Add(new Injection4());
        }
        GUILayout.EndHorizontal();

        field.SetValue(m_LuaInjectionData, injections);

        if (GUI.changed)
        {
            EditorUtility.SetDirty(m_LuaInjectionData);
        }
    }

    private List<T> DrawArray<T>(List<T> array, bool isDict) where T : Injection, new()
    {
        for (int index = 0; index < array.Count; index++)
        {
            GUILayout.BeginHorizontal();

            Undo.RecordObject(m_LuaInjectionData, "Injection.Count");
            bool remove = GUILayout.Button("×", GUILayout.Width(20F), GUILayout.Height(14F));

            T injection = array[index] ?? new T();

            GUILayout.Label("Type:", GUILayout.Width(40F));
            Undo.RecordObject(m_LuaInjectionData, "Injection.Type");
            injection.Type = (InjectionType)EditorGUILayout.EnumPopup(injection.Type, GUILayout.Width(75F));
            if (injection.Type == InjectionType.List || injection.Type == InjectionType.Dict)
            {
                if (typeof(T) == typeof(Injection))
                {
                    injection.Type = InjectionType.String;
                }
            }

            GUI.enabled = index > 0;
            Undo.RecordObject(m_LuaInjectionData, "Injection.Up");
            bool up = GUILayout.Button("▲", GUILayout.Width(20F), GUILayout.Height(14F));
            GUI.enabled = index < array.Count - 1;
            Undo.RecordObject(m_LuaInjectionData, "Injection.Down");
            bool down = GUILayout.Button("▼", GUILayout.Width(20F), GUILayout.Height(14F));
            GUI.enabled = true;

            if (isDict)
            {
                GUILayout.Label("Name:", GUILayout.Width(40F));
                injection.Name = HotKeyTextField(injection.Name ?? "", GUILayout.MaxWidth(120F));
            }
            else
            {
                GUILayout.Label("Index:", GUILayout.Width(40F));
                GUI.enabled = false;
                EditorGUILayout.IntField(index + 1, GUILayout.Width(30F));
                GUI.enabled = true;
            }

            object value = injection.Value;
            object newValue = DrawValue<T>(injection.Type, value);
            if (newValue != value)
            {
                injection.Value = newValue;
            }

            GUILayout.EndHorizontal();

            if (remove)
            {
                array.RemoveAt(index);
                index--;
            }
            else if (up)
            {
                array[index] = array[index - 1];
                array[index - 1] = injection;
            }
            else if (down)
            {
                array[index] = array[index + 1];
                array[index + 1] = injection;
            }
            else
            {
                array[index] = injection;
            }
        }
        return array;
    }

    private object DrawValue<T>(InjectionType type, object value) where T : Injection, new()
    {
        switch (type)
        {
            case InjectionType.String:
                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                value = HotKeyTextArea(JsonParser.ObjectToString(value, ""));
                break;
            case InjectionType.Int:
                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                value = EditorGUILayout.IntField(JsonParser.ObjectToInt(value));
                break;
            case InjectionType.Float:
                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                value = EditorGUILayout.FloatField(JsonParser.ObjectToFloat(value));
                break;
            case InjectionType.Boolean:
                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                value = EditorGUILayout.Toggle(JsonParser.ObjectToBool(value));
                break;
            case InjectionType.Object:
                Object obj = value as Object;
                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                value = EditorGUILayout.ObjectField(obj, typeof(Object), true);
                break;
            case InjectionType.GameObject:
                GameObject go = value as GameObject;
                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                value = EditorGUILayout.ObjectField(go, typeof(GameObject), true);
                break;
            case InjectionType.Transform:
                Transform trans = value as Transform;
                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                value = EditorGUILayout.ObjectField(trans, typeof(Transform), true);
                break;
            case InjectionType.Collider:
                Collider collider = value as Collider;
                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                value = EditorGUILayout.ObjectField(collider, typeof(Collider), true);
                break;
            case InjectionType.Behaviour:
                MonoBehaviour behaviour = value as MonoBehaviour;

                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                MonoBehaviour currentBehaviour = EditorGUILayout.ObjectField(behaviour, typeof(MonoBehaviour), true) as MonoBehaviour;
                if (currentBehaviour)
                {
                    MonoBehaviour[] behaviours = currentBehaviour.GetComponents<MonoBehaviour>();
                    int currentIndex = behaviours.IndexOf(currentBehaviour);
                    int behaviourCount = behaviours.Length;
                    string[] behaviourNames = new string[behaviourCount];
                    int[] behaviourIndexs = new int[behaviourCount];
                    for (int index = 0; index < behaviourCount; index++)
                    {
                        behaviourNames[index] = behaviours[index].GetType().Name;
                        behaviourIndexs[index] = index;
                    }

                    Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                    int behaviourIndex = EditorGUILayout.IntPopup(currentIndex, behaviourNames, behaviourIndexs);
                    if (behaviourIndex != currentIndex)
                    {
                        currentBehaviour = behaviours[behaviourIndex];
                    }
                }
                value = currentBehaviour;
                break;
            case InjectionType.Renderer:
                Renderer renderer = value as Renderer;
                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                value = EditorGUILayout.ObjectField(renderer, typeof(Renderer), true);
                break;
            case InjectionType.Curve:
                AnimationCurve curve = value as AnimationCurve;
                GUILayout.Label("Value:", GUILayout.Width(40F));
                Undo.RecordObject(m_LuaInjectionData, "Injection.Value");
                value = EditorGUILayout.CurveField(curve ?? new AnimationCurve());
                break;
            case InjectionType.List:
            case InjectionType.Dict:
                bool isDict = type == InjectionType.Dict;
                if (typeof(T) == typeof(Injection4))
                {
                    if (!(value is List<Injection3>))
                    {
                        value = new List<Injection3>();
                    }
                    DrawArrayValue(value as List<Injection3>, isDict);
                }
                else if (typeof(T) == typeof(Injection3))
                {
                    if (!(value is List<Injection2>))
                    {
                        value = new List<Injection2>();
                    }
                    DrawArrayValue(value as List<Injection2>, isDict);
                }
                else if (typeof(T) == typeof(Injection2))
                {
                    if (!(value is List<Injection2>))
                    {
                        value = new List<Injection2>();
                    }
                    DrawArrayValue(value as List<Injection1>, isDict);
                }
                else if (typeof(T) == typeof(Injection1))
                {
                    if (!(value is List<Injection2>))
                    {
                        value = new List<Injection2>();
                    }
                    DrawArrayValue(value as List<Injection>, isDict);
                }
                break;
        }
        return value;
    }

    private void DrawArrayValue<T>(List<T> array, bool isDict) where T : Injection, new()
    {
        GUILayout.Label("Size:", GUILayout.Width(40F));
        GUI.enabled = false;
        EditorGUILayout.IntField(array.Count);
        GUI.enabled = true;
        Undo.RecordObject(m_LuaInjectionData, "Injection.Append");
        if (GUILayout.Button("+", GUILayout.Width(20F), GUILayout.Height(14F)))
        {
            array.Add(new T());
        }
        GUILayout.EndHorizontal();
        GUILayout.BeginHorizontal();
        GUILayout.Space(20F);
        GUILayout.BeginVertical();
        DrawArray(array, isDict);
        GUILayout.EndVertical();
    }

    public static string HotKeyTextField(string value, params GUILayoutOption[] options)
    {
        int textFieldID = GUIUtility.GetControlID("TextField".GetHashCode(), FocusType.Keyboard) + 1;
        if (textFieldID == 0)
        {
            return value;
        }
        string newValue = HandleHotKey(textFieldID) ?? value;
        if (newValue != value)
        {
            GUI.changed = true;
        }
        return GUILayout.TextField(newValue, options);
    }

    public static string HotKeyTextArea(string value, params GUILayoutOption[] options)
    {
        int textAreaID = GUIUtility.GetControlID("TextArea".GetHashCode(), FocusType.Keyboard) + 1;
        if (textAreaID == 0)
        {
            return value;
        }
        string newValue = HandleHotKey(textAreaID) ?? value;
        if (newValue != value)
        {
            GUI.changed = true;
        }
        return GUILayout.TextArea(newValue, options);
    }

    public static string HandleHotKey(int controlID)
    {
        if (controlID == GUIUtility.keyboardControl)
        {
            if (Event.current.type == EventType.KeyUp &&
                (Event.current.modifiers == EventModifiers.Control || Event.current.modifiers == EventModifiers.Command))
            {
                if (Event.current.keyCode == KeyCode.X)
                {
                    Event.current.Use();
                    TextEditor editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
                    editor.Copy();
                    editor.Delete();
                    return editor.text;
                }
                else if (Event.current.keyCode == KeyCode.C)
                {
                    Event.current.Use();
                    TextEditor editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
                    editor.Copy();
                }
                else if (Event.current.keyCode == KeyCode.V)
                {
                    Event.current.Use();
                    TextEditor editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
                    editor.Paste();
#if UNITY_5_3_OR_NEWER
                    return editor.text;
#else
                    return editor.content.text;
#endif
                }
                else if (Event.current.keyCode == KeyCode.A)
                {
                    Event.current.Use();
                    TextEditor editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
                    editor.SelectAll();
                }
            }
        }
        return null;
    }
}
